#region Copyright & License

//
// Copyright 2001-2005 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#endregion

using System;
using System.Collections;
using System.Globalization;
using System.IO;
using log4net.Core;
using log4net.Filter;
using log4net.Layout;
using log4net.Util;

namespace log4net.Appender {
  /// <summary>
  /// Abstract base class implementation of <see cref="IAppender"/>. 
  /// </summary>
  /// <remarks>
  /// <para>
  /// This class provides the code for common functionality, such 
  /// as support for threshold filtering and support for general filters.
  /// </para>
  /// <para>
  /// Appenders can also implement the <see cref="IOptionHandler"/> interface. Therefore
  /// they would require that the <see cref="IOptionHandler.ActivateOptions()"/> method
  /// be called after the appenders properties have been configured.
  /// </para>
  /// </remarks>
  /// <author>Nicko Cadell</author>
  /// <author>Gert Driesen</author>
  public abstract class AppenderSkeleton : IAppender, IBulkAppender, IOptionHandler {
    #region Protected Instance Constructors

    /// <summary>
    /// Default constructor
    /// </summary>
    /// <remarks>
    /// <para>Empty default constructor</para>
    /// </remarks>
    protected AppenderSkeleton() {
      m_errorHandler = new OnlyOnceErrorHandler(GetType().Name);
    }

    #endregion Protected Instance Constructors

    #region Finalizer

    /// <summary>
    /// Finalizes this appender by calling the implementation's 
    /// <see cref="Close"/> method.
    /// </summary>
    /// <remarks>
    /// <para>
    /// If this appender has not been closed then the <c>Finalize</c> method
    /// will call <see cref="Close"/>.
    /// </para>
    /// </remarks>
    ~AppenderSkeleton() {
      // An appender might be closed then garbage collected. 
      // There is no point in closing twice.
      if (!m_closed) {
        LogLog.Debug("AppenderSkeleton: Finalizing appender named [" + m_name + "].");
        Close();
      }
    }

    #endregion Finalizer

    #region Public Instance Properties

    /// <summary>
    /// Gets or sets the threshold <see cref="Level"/> of this appender.
    /// </summary>
    /// <value>
    /// The threshold <see cref="Level"/> of the appender. 
    /// </value>
    /// <remarks>
    /// <para>
    /// All log events with lower level than the threshold level are ignored 
    /// by the appender.
    /// </para>
    /// <para>
    /// In configuration files this option is specified by setting the
    /// value of the <see cref="Threshold"/> option to a level
    /// string, such as "DEBUG", "INFO" and so on.
    /// </para>
    /// </remarks>
    public Level Threshold {
      get { return m_threshold; }
      set { m_threshold = value; }
    }

    /// <summary>
    /// Gets or sets the <see cref="IErrorHandler"/> for this appender.
    /// </summary>
    /// <value>The <see cref="IErrorHandler"/> of the appender</value>
    /// <remarks>
    /// <para>
    /// The <see cref="AppenderSkeleton"/> provides a default 
    /// implementation for the <see cref="ErrorHandler"/> property. 
    /// </para>
    /// </remarks>
    public virtual IErrorHandler ErrorHandler {
      get { return m_errorHandler; }
      set {
        lock (this) {
          if (value == null) // We do not throw exception here since the cause is probably a
            // bad config file.
            LogLog.Warn("AppenderSkeleton: You have tried to set a null error-handler.");
          else
            m_errorHandler = value;
        }
      }
    }

    /// <summary>
    /// The filter chain.
    /// </summary>
    /// <value>The head of the filter chain filter chain.</value>
    /// <remarks>
    /// <para>
    /// Returns the head Filter. The Filters are organized in a linked list
    /// and so all Filters on this Appender are available through the result.
    /// </para>
    /// </remarks>
    public virtual IFilter FilterHead {
      get { return m_headFilter; }
    }

    /// <summary>
    /// Gets or sets the <see cref="ILayout"/> for this appender.
    /// </summary>
    /// <value>The layout of the appender.</value>
    /// <remarks>
    /// <para>
    /// See <see cref="RequiresLayout"/> for more information.
    /// </para>
    /// </remarks>
    /// <seealso cref="RequiresLayout"/>
    public virtual ILayout Layout {
      get { return m_layout; }
      set { m_layout = value; }
    }

    #endregion

    #region Implementation of IOptionHandler

    /// <summary>
    /// Initialize the appender based on the options set
    /// </summary>
    /// <remarks>
    /// <para>
    /// This is part of the <see cref="IOptionHandler"/> delayed object
    /// activation scheme. The <see cref="ActivateOptions"/> method must 
    /// be called on this object after the configuration properties have
    /// been set. Until <see cref="ActivateOptions"/> is called this
    /// object is in an undefined state and must not be used. 
    /// </para>
    /// <para>
    /// If any of the configuration properties are modified then 
    /// <see cref="ActivateOptions"/> must be called again.
    /// </para>
    /// </remarks>
    public virtual void ActivateOptions() {}

    #endregion Implementation of IOptionHandler

    #region Implementation of IAppender

    /// <summary>
    /// Gets or sets the name of this appender.
    /// </summary>
    /// <value>The name of the appender.</value>
    /// <remarks>
    /// <para>
    /// The name uniquely identifies the appender.
    /// </para>
    /// </remarks>
    public string Name {
      get { return m_name; }
      set { m_name = value; }
    }

    /// <summary>
    /// Closes the appender and release resources.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Release any resources allocated within the appender such as file handles, 
    /// network connections, etc.
    /// </para>
    /// <para>
    /// It is a programming error to append to a closed appender.
    /// </para>
    /// <para>
    /// This method cannot be overridden by subclasses. This method 
    /// delegates the closing of the appender to the <see cref="OnClose"/>
    /// method which must be overridden in the subclass.
    /// </para>
    /// </remarks>
    public void Close() {
      // This lock prevents the appender being closed while it is still appending
      lock (this) {
        if (!m_closed) {
          OnClose();
          m_closed = true;
        }
      }
    }

    /// <summary>
    /// Performs threshold checks and invokes filters before 
    /// delegating actual logging to the subclasses specific 
    /// <see cref="Append(LoggingEvent)"/> method.
    /// </summary>
    /// <param name="loggingEvent">The event to log.</param>
    /// <remarks>
    /// <para>
    /// This method cannot be overridden by derived classes. A
    /// derived class should override the <see cref="Append(LoggingEvent)"/> method
    /// which is called by this method.
    /// </para>
    /// <para>
    /// The implementation of this method is as follows:
    /// </para>
    /// <para>
    /// <list type="bullet">
    ///		<item>
    ///			<description>
    ///			Checks that the severity of the <paramref name="loggingEvent"/>
    ///			is greater than or equal to the <see cref="Threshold"/> of this
    ///			appender.</description>
    ///		</item>
    ///		<item>
    ///			<description>
    ///			Checks that the <see cref="IFilter"/> chain accepts the 
    ///			<paramref name="loggingEvent"/>.
    ///			</description>
    ///		</item>
    ///		<item>
    ///			<description>
    ///			Calls <see cref="PreAppendCheck()"/> and checks that 
    ///			it returns <c>true</c>.</description>
    ///		</item>
    /// </list>
    /// </para>
    /// <para>
    /// If all of the above steps succeed then the <paramref name="loggingEvent"/>
    /// will be passed to the abstract <see cref="Append(LoggingEvent)"/> method.
    /// </para>
    /// </remarks>
    public void DoAppend(LoggingEvent loggingEvent) {
      // This lock is absolutely critical for correct formatting
      // of the message in a multi-threaded environment.  Without
      // this, the message may be broken up into elements from
      // multiple thread contexts (like get the wrong thread ID).

      lock (this) {
        if (m_closed) {
          ErrorHandler.Error("Attempted to append to closed appender named [" + m_name + "].");
          return;
        }

        // prevent re-entry
        if (m_recursiveGuard)
          return;

        try {
          m_recursiveGuard = true;

          if (FilterEvent(loggingEvent) && PreAppendCheck())
            Append(loggingEvent);
        } catch (Exception ex) {
          ErrorHandler.Error("Failed in DoAppend", ex);
        }
#if !MONO && !NET_2_0
          // on .NET 2.0 (and higher) and Mono (all profiles), 
          // exceptions that do not derive from System.Exception will be
          // wrapped in a RuntimeWrappedException by the runtime, and as
          // such will be catched by the catch clause above
        catch {
          // Catch handler for non System.Exception types
          ErrorHandler.Error("Failed in DoAppend (unknown exception)");
        }
#endif
        finally {
          m_recursiveGuard = false;
        }
      }
    }

    #endregion Implementation of IAppender

    #region Implementation of IBulkAppender

    /// <summary>
    /// Performs threshold checks and invokes filters before 
    /// delegating actual logging to the subclasses specific 
    /// <see cref="Append(LoggingEvent[])"/> method.
    /// </summary>
    /// <param name="loggingEvents">The array of events to log.</param>
    /// <remarks>
    /// <para>
    /// This method cannot be overridden by derived classes. A
    /// derived class should override the <see cref="Append(LoggingEvent[])"/> method
    /// which is called by this method.
    /// </para>
    /// <para>
    /// The implementation of this method is as follows:
    /// </para>
    /// <para>
    /// <list type="bullet">
    ///		<item>
    ///			<description>
    ///			Checks that the severity of the <paramref name="loggingEvent"/>
    ///			is greater than or equal to the <see cref="Threshold"/> of this
    ///			appender.</description>
    ///		</item>
    ///		<item>
    ///			<description>
    ///			Checks that the <see cref="IFilter"/> chain accepts the 
    ///			<paramref name="loggingEvent"/>.
    ///			</description>
    ///		</item>
    ///		<item>
    ///			<description>
    ///			Calls <see cref="PreAppendCheck()"/> and checks that 
    ///			it returns <c>true</c>.</description>
    ///		</item>
    /// </list>
    /// </para>
    /// <para>
    /// If all of the above steps succeed then the <paramref name="loggingEvents"/>
    /// will be passed to the <see cref="Append(LoggingEvent[])"/> method.
    /// </para>
    /// </remarks>
    public void DoAppend(LoggingEvent[] loggingEvents) {
      // This lock is absolutely critical for correct formatting
      // of the message in a multi-threaded environment.  Without
      // this, the message may be broken up into elements from
      // multiple thread contexts (like get the wrong thread ID).

      lock (this) {
        if (m_closed) {
          ErrorHandler.Error("Attempted to append to closed appender named [" + m_name + "].");
          return;
        }

        // prevent re-entry
        if (m_recursiveGuard)
          return;

        try {
          m_recursiveGuard = true;

          var filteredEvents = new ArrayList(loggingEvents.Length);

          foreach (LoggingEvent loggingEvent in loggingEvents)
            if (FilterEvent(loggingEvent))
              filteredEvents.Add(loggingEvent);

          if (filteredEvents.Count > 0 && PreAppendCheck())
            Append((LoggingEvent[]) filteredEvents.ToArray(typeof (LoggingEvent)));
        } catch (Exception ex) {
          ErrorHandler.Error("Failed in Bulk DoAppend", ex);
        }
#if !MONO && !NET_2_0
          // on .NET 2.0 (and higher) and Mono (all profiles), 
          // exceptions that do not derive from System.Exception will be
          // wrapped in a RuntimeWrappedException by the runtime, and as
          // such will be catched by the catch clause above
        catch {
          // Catch handler for non System.Exception types
          ErrorHandler.Error("Failed in Bulk DoAppend (unknown exception)");
        }
#endif
        finally {
          m_recursiveGuard = false;
        }
      }
    }

    #endregion Implementation of IBulkAppender

    /// <summary>
    /// Test if the logging event should we output by this appender
    /// </summary>
    /// <param name="loggingEvent">the event to test</param>
    /// <returns><c>true</c> if the event should be output, <c>false</c> if the event should be ignored</returns>
    /// <remarks>
    /// <para>
    /// This method checks the logging event against the threshold level set
    /// on this appender and also against the filters specified on this
    /// appender.
    /// </para>
    /// <para>
    /// The implementation of this method is as follows:
    /// </para>
    /// <para>
    /// <list type="bullet">
    ///		<item>
    ///			<description>
    ///			Checks that the severity of the <paramref name="loggingEvent"/>
    ///			is greater than or equal to the <see cref="Threshold"/> of this
    ///			appender.</description>
    ///		</item>
    ///		<item>
    ///			<description>
    ///			Checks that the <see cref="IFilter"/> chain accepts the 
    ///			<paramref name="loggingEvent"/>.
    ///			</description>
    ///		</item>
    /// </list>
    /// </para>
    /// </remarks>
    protected virtual bool FilterEvent(LoggingEvent loggingEvent) {
      if (!IsAsSevereAsThreshold(loggingEvent.Level))
        return false;

      IFilter f = FilterHead;

      while (f != null)
        switch (f.Decide(loggingEvent)) {
          case FilterDecision.Deny:
            return false; // Return without appending

          case FilterDecision.Accept:
            f = null; // Break out of the loop
            break;

          case FilterDecision.Neutral:
            f = f.Next; // Move to next filter
            break;
        }

      return true;
    }

    #region Public Instance Methods

    /// <summary>
    /// Adds a filter to the end of the filter chain.
    /// </summary>
    /// <param name="filter">the filter to add to this appender</param>
    /// <remarks>
    /// <para>
    /// The Filters are organized in a linked list.
    /// </para>
    /// <para>
    /// Setting this property causes the new filter to be pushed onto the 
    /// back of the filter chain.
    /// </para>
    /// </remarks>
    public virtual void AddFilter(IFilter filter) {
      if (filter == null)
        throw new ArgumentNullException("filter param must not be null");

      if (m_headFilter == null)
        m_headFilter = m_tailFilter = filter;
      else {
        m_tailFilter.Next = filter;
        m_tailFilter = filter;
      }
    }

    /// <summary>
    /// Clears the filter list for this appender.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Clears the filter list for this appender.
    /// </para>
    /// </remarks>
    public virtual void ClearFilters() {
      m_headFilter = m_tailFilter = null;
    }

    #endregion Public Instance Methods

    #region Protected Instance Methods

    /// <summary>
    /// Tests if this appender requires a <see cref="Layout"/> to be set.
    /// </summary>
    /// <remarks>
    /// <para>
    /// In the rather exceptional case, where the appender 
    /// implementation admits a layout but can also work without it, 
    /// then the appender should return <c>true</c>.
    /// </para>
    /// <para>
    /// This default implementation always returns <c>true</c>.
    /// </para>
    /// </remarks>
    /// <returns>
    /// <c>true</c> if the appender requires a layout object, otherwise <c>false</c>.
    /// </returns>
    protected virtual bool RequiresLayout {
      get { return false; }
    }

    /// <summary>
    /// Checks if the message level is below this appender's threshold.
    /// </summary>
    /// <param name="level"><see cref="Level"/> to test against.</param>
    /// <remarks>
    /// <para>
    /// If there is no threshold set, then the return value is always <c>true</c>.
    /// </para>
    /// </remarks>
    /// <returns>
    /// <c>true</c> if the <paramref name="level"/> meets the <see cref="Threshold"/> 
    /// requirements of this appender.
    /// </returns>
    protected virtual bool IsAsSevereAsThreshold(Level level) {
      return ((m_threshold == null) || level >= m_threshold);
    }

    /// <summary>
    /// Is called when the appender is closed. Derived classes should override 
    /// this method if resources need to be released.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Releases any resources allocated within the appender such as file handles, 
    /// network connections, etc.
    /// </para>
    /// <para>
    /// It is a programming error to append to a closed appender.
    /// </para>
    /// </remarks>
    protected virtual void OnClose() {
      // Do nothing by default
    }

    /// <summary>
    /// Subclasses of <see cref="AppenderSkeleton"/> should implement this method 
    /// to perform actual logging.
    /// </summary>
    /// <param name="loggingEvent">The event to append.</param>
    /// <remarks>
    /// <para>
    /// A subclass must implement this method to perform
    /// logging of the <paramref name="loggingEvent"/>.
    /// </para>
    /// <para>This method will be called by <see cref="DoAppend(LoggingEvent)"/>
    /// if all the conditions listed for that method are met.
    /// </para>
    /// <para>
    /// To restrict the logging of events in the appender
    /// override the <see cref="PreAppendCheck()"/> method.
    /// </para>
    /// </remarks>
    protected abstract void Append(LoggingEvent loggingEvent);

    /// <summary>
    /// Append a bulk array of logging events.
    /// </summary>
    /// <param name="loggingEvents">the array of logging events</param>
    /// <remarks>
    /// <para>
    /// This base class implementation calls the <see cref="Append(LoggingEvent)"/>
    /// method for each element in the bulk array.
    /// </para>
    /// <para>
    /// A sub class that can better process a bulk array of events should
    /// override this method in addition to <see cref="Append(LoggingEvent)"/>.
    /// </para>
    /// </remarks>
    protected virtual void Append(LoggingEvent[] loggingEvents) {
      foreach (LoggingEvent loggingEvent in loggingEvents)
        Append(loggingEvent);
    }

    /// <summary>
    /// Called before <see cref="Append(LoggingEvent)"/> as a precondition.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This method is called by <see cref="DoAppend(LoggingEvent)"/>
    /// before the call to the abstract <see cref="Append(LoggingEvent)"/> method.
    /// </para>
    /// <para>
    /// This method can be overridden in a subclass to extend the checks 
    /// made before the event is passed to the <see cref="Append(LoggingEvent)"/> method.
    /// </para>
    /// <para>
    /// A subclass should ensure that they delegate this call to
    /// this base class if it is overridden.
    /// </para>
    /// </remarks>
    /// <returns><c>true</c> if the call to <see cref="Append(LoggingEvent)"/> should proceed.</returns>
    protected virtual bool PreAppendCheck() {
      if ((m_layout == null) && RequiresLayout) {
        ErrorHandler.Error("AppenderSkeleton: No layout set for the appender named [" + m_name + "].");
        return false;
      }

      return true;
    }

    /// <summary>
    /// Renders the <see cref="LoggingEvent"/> to a string.
    /// </summary>
    /// <param name="loggingEvent">The event to render.</param>
    /// <returns>The event rendered as a string.</returns>
    /// <remarks>
    /// <para>
    /// Helper method to render a <see cref="LoggingEvent"/> to 
    /// a string. This appender must have a <see cref="Layout"/>
    /// set to render the <paramref name="loggingEvent"/> to 
    /// a string.
    /// </para>
    /// <para>If there is exception data in the logging event and 
    /// the layout does not process the exception, this method 
    /// will append the exception text to the rendered string.
    /// </para>
    /// <para>
    /// Where possible use the alternative version of this method
    /// <see cref="RenderLoggingEvent(TextWriter,LoggingEvent)"/>.
    /// That method streams the rendering onto an existing Writer
    /// which can give better performance if the caller already has
    /// a <see cref="TextWriter"/> open and ready for writing.
    /// </para>
    /// </remarks>
    protected string RenderLoggingEvent(LoggingEvent loggingEvent) {
      // Create the render writer on first use
      if (m_renderWriter == null)
        m_renderWriter = new ReusableStringWriter(CultureInfo.InvariantCulture);

      // Reset the writer so we can reuse it
      m_renderWriter.Reset(c_renderBufferMaxCapacity, c_renderBufferSize);

      RenderLoggingEvent(m_renderWriter, loggingEvent);
      return m_renderWriter.ToString();
    }

    /// <summary>
    /// Renders the <see cref="LoggingEvent"/> to a string.
    /// </summary>
    /// <param name="loggingEvent">The event to render.</param>
    /// <param name="writer">The TextWriter to write the formatted event to</param>
    /// <remarks>
    /// <para>
    /// Helper method to render a <see cref="LoggingEvent"/> to 
    /// a string. This appender must have a <see cref="Layout"/>
    /// set to render the <paramref name="loggingEvent"/> to 
    /// a string.
    /// </para>
    /// <para>If there is exception data in the logging event and 
    /// the layout does not process the exception, this method 
    /// will append the exception text to the rendered string.
    /// </para>
    /// <para>
    /// Use this method in preference to <see cref="RenderLoggingEvent(LoggingEvent)"/>
    /// where possible. If, however, the caller needs to render the event
    /// to a string then <see cref="RenderLoggingEvent(LoggingEvent)"/> does
    /// provide an efficient mechanism for doing so.
    /// </para>
    /// </remarks>
    protected void RenderLoggingEvent(TextWriter writer, LoggingEvent loggingEvent) {
      if (m_layout == null)
        throw new InvalidOperationException("A layout must be set");

      if (m_layout.IgnoresException) {
        string exceptionStr = loggingEvent.GetExceptionString();
        if (exceptionStr != null && exceptionStr.Length > 0) {
          // render the event and the exception
          m_layout.Format(writer, loggingEvent);
          writer.WriteLine(exceptionStr);
        } else // there is no exception to render
          m_layout.Format(writer, loggingEvent);
      } else // The layout will render the exception
        m_layout.Format(writer, loggingEvent);
    }

    #endregion

    #region Private Instance Fields

    /// <summary>
    /// Flag indicating if this appender is closed.
    /// </summary>
    /// <remarks>
    /// See <see cref="Close"/> for more information.
    /// </remarks>
    bool m_closed;

    /// <summary>
    /// It is assumed and enforced that errorHandler is never null.
    /// </summary>
    /// <remarks>
    /// <para>
    /// It is assumed and enforced that errorHandler is never null.
    /// </para>
    /// <para>
    /// See <see cref="ErrorHandler"/> for more information.
    /// </para>
    /// </remarks>
    IErrorHandler m_errorHandler;

    /// <summary>
    /// The first filter in the filter chain.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Set to <c>null</c> initially.
    /// </para>
    /// <para>
    /// See <see cref="IFilter"/> for more information.
    /// </para>
    /// </remarks>
    IFilter m_headFilter;

    /// <summary>
    /// The layout of this appender.
    /// </summary>
    /// <remarks>
    /// See <see cref="Layout"/> for more information.
    /// </remarks>
    ILayout m_layout;

    /// <summary>
    /// The name of this appender.
    /// </summary>
    /// <remarks>
    /// See <see cref="Name"/> for more information.
    /// </remarks>
    string m_name;

    /// <summary>
    /// The guard prevents an appender from repeatedly calling its own DoAppend method
    /// </summary>
    bool m_recursiveGuard;

    /// <summary>
    /// StringWriter used to render events
    /// </summary>
    ReusableStringWriter m_renderWriter;

    /// <summary>
    /// The last filter in the filter chain.
    /// </summary>
    /// <remarks>
    /// See <see cref="IFilter"/> for more information.
    /// </remarks>
    IFilter m_tailFilter;

    /// <summary>
    /// The level threshold of this appender.
    /// </summary>
    /// <remarks>
    /// <para>
    /// There is no level threshold filtering by default.
    /// </para>
    /// <para>
    /// See <see cref="Threshold"/> for more information.
    /// </para>
    /// </remarks>
    Level m_threshold;

    #endregion Private Instance Fields

    #region Constants

    /// <summary>
    /// Maximum buffer size before it is recycled
    /// </summary>
    const int c_renderBufferMaxCapacity = 1024;

    /// <summary>
    /// Initial buffer size
    /// </summary>
    const int c_renderBufferSize = 256;

    #endregion
  }
}